﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using Mesuro.Graphing;
using System.Collections.ObjectModel;
using System.Diagnostics;
using Mesuro.Common;
using System.Collections.Specialized;
using System.Collections;
using Mesuro.Graphing.Utilities;
using System.Xml;
using System.Windows.Controls.Primitives;
using System.Windows.Markup;
using System.IO;
using Mesuro.GUI.Utilities;

namespace Mesuro.GUI {
	/// <summary>
	/// Interaction logic for Sheet.xaml
	/// </summary>
	public partial class Sheet : MultiSelector, IXmlSerialisable {
		public const string FORMAT_CanvasHostableEnumeration = "MesuroSheetHostableCollection";
		#region DP: SheetName
		public static readonly DependencyProperty SheetNameProperty = DependencyProperty.RegisterAttached( "SheetName", typeof( String ), typeof( Sheet ), new FrameworkPropertyMetadata( "Unnamed Sheet", FrameworkPropertyMetadataOptions.None, SheetNameChangedStatic ) );
		public String SheetName {
			get { return (String)GetValue( SheetNameProperty ); }
			set { SetValue( SheetNameProperty, value ); }
		}
		public static String GetSheetName( DependencyObject element ) { return (String)element.GetValue( SheetNameProperty ); }
		public static void SetSheetName( DependencyObject element, String value ) { element.SetValue( SheetNameProperty, value ); }
		private static void SheetNameChangedStatic( DependencyObject sender, DependencyPropertyChangedEventArgs e ) { }
		#endregion

		#region DP: IsEmpty
		public static readonly DependencyProperty IsEmptyProperty = DependencyProperty.RegisterAttached( "IsEmpty", typeof( bool ), typeof( Sheet ), new FrameworkPropertyMetadata( true, FrameworkPropertyMetadataOptions.None, IsEmptyChangedStatic ) );
		public bool IsEmpty {
			get { return (bool)GetValue( IsEmptyProperty ); }
			set { SetValue( IsEmptyProperty, value ); }
		}
		public static bool GetIsEmpty( DependencyObject element ) { return (bool)element.GetValue( IsEmptyProperty ); }
		public static void SetIsEmpty( DependencyObject element, bool value ) { element.SetValue( IsEmptyProperty, value ); }
		private static void IsEmptyChangedStatic( DependencyObject sender, DependencyPropertyChangedEventArgs e ) { }
		#endregion

		//private CommonPropertyProxy _selection;
		//public dynamic Selection {
		//    get { return _selection; }
		//    set { _selection = value; }
		//}

		static Sheet() {
			FocusableProperty.OverrideMetadata( typeof( Sheet ), new FrameworkPropertyMetadata( true ) );
		}

		public Sheet()
			: this( "Unnamed Sheet" ) { }

		public Sheet( String name ) {
			SheetName = name;
			//_selection = new CommonPropertyProxy( SelectedItems );
			InitializeComponent();
		}

		protected override void OnItemsChanged( NotifyCollectionChangedEventArgs e ) {
			base.OnItemsChanged( e );
			if (e.Action.HasFlag( NotifyCollectionChangedAction.Add ) || e.Action.HasFlag( NotifyCollectionChangedAction.Replace ))
				if (e.NewItems != null && e.NewItems.Count > 0)
					foreach (UIElement el in e.NewItems.OfType<UIElement>())
						el.AddHandler( UIElement.MouseLeftButtonUpEvent, (MouseButtonEventHandler)OnChildMouseLeftButtonUp, true );

			if (e.Action.HasFlag( NotifyCollectionChangedAction.Remove ) || e.Action.HasFlag( NotifyCollectionChangedAction.Reset ) || e.Action.HasFlag( NotifyCollectionChangedAction.Replace ))
				if (e.OldItems != null && e.OldItems.Count > 0)
					foreach (UIElement el in e.OldItems.OfType<UIElement>())
						el.RemoveHandler( UIElement.MouseLeftButtonUpEvent, (MouseButtonEventHandler)OnChildMouseLeftButtonUp );

			IsEmpty = Items.IsEmpty;
		}

		#region Item Host Creation
		protected override bool IsItemItsOwnContainerOverride( object item ) {
			return item is ResizeHost;
		}

		protected override DependencyObject GetContainerForItemOverride() {
			return new ResizeHost();
		}
		#endregion

		#region Object Z Index
		private void ObjectOrderFront() {
			IEnumerable containers = Items.OfType<DependencyObject>()
				.Select( ( x ) => ItemContainerGenerator.ContainerFromItem( x ) )
				.OrderBy( ( x ) => (int)x.GetValue( Panel.ZIndexProperty ) );

			int firstIndexForSelected = Items.Count - SelectedItems.Count;
			int selectedSeen = 0;
			int i = 0;

			foreach (DependencyObject h in containers) {
				if (SelectedItems.Contains( ItemContainerGenerator.ItemFromContainer( h ) )) {
					selectedSeen++;
					h.SetValue( Panel.ZIndexProperty, firstIndexForSelected + selectedSeen );
				} else
					h.SetValue( Panel.ZIndexProperty, i - selectedSeen );
				i++;
			}
		}
		private void ObjectOrderRaise() {
			IEnumerable containers = Items.OfType<DependencyObject>()
				.Select( ( x ) => ItemContainerGenerator.ContainerFromItem( x ) )
				.OrderBy( ( x ) => (int)x.GetValue( Panel.ZIndexProperty ) );

			int nSelected = SelectedItems.Count;
			int selectedSeen = 0;
			int i = 0;

			foreach (DependencyObject h in containers) {
				if (SelectedItems.Contains( ItemContainerGenerator.ItemFromContainer( h ) )) {
					selectedSeen++;
					h.SetValue( Panel.ZIndexProperty, i + 1 );
				} else
					h.SetValue( Panel.ZIndexProperty, i - selectedSeen );
				i++;
			}
		}
		private void ObjectOrderLower() {
			IEnumerable containers = Items.OfType<DependencyObject>()
				.Select( ( x ) => ItemContainerGenerator.ContainerFromItem( x ) )
				.OrderByDescending( ( x ) => (int)x.GetValue( Panel.ZIndexProperty ) );

			int nItems = Items.Count;
			int nSelected = SelectedItems.Count;
			int selectedSeen = 0;
			int i = 0;

			foreach (DependencyObject h in containers) {
				if (SelectedItems.Contains( ItemContainerGenerator.ItemFromContainer( h ) )) {
					h.SetValue( Panel.ZIndexProperty, nItems - i - 1 );
					selectedSeen++;
				} else
					h.SetValue( Panel.ZIndexProperty, nItems - i + selectedSeen );
				i++;
			}
		}
		private void ObjectOrderBack() {
			IEnumerable containers = Items.OfType<DependencyObject>()
				.Select( ( x ) => ItemContainerGenerator.ContainerFromItem( x ) )
				.OrderBy( ( x ) => (int)x.GetValue( Panel.ZIndexProperty ) );

			int nItems = Items.Count;
			int nSelected = SelectedItems.Count;
			int selectedSeen = 0;
			int i = 0;

			foreach (DependencyObject h in containers) {
				if (SelectedItems.Contains( ItemContainerGenerator.ItemFromContainer( h ) )) {
					h.SetValue( Panel.ZIndexProperty, selectedSeen );
					selectedSeen++;
				} else
					h.SetValue( Panel.ZIndexProperty, nSelected + i );
				i++;
			}
		}
		#endregion

		private void CopySelected() {
			DataObject dobj = new DataObject();
			List<object> list = new List<object>( SelectedItems.Cast<object>() );
			//dobj.SetData( FORMAT_CanvasHostableEnumeration,
			//    list.Aggregate( new StringBuilder( "<x:Array Type=\"object\">" ),
			//        ( sb, o ) => sb.Append( XamlWriter.Save( o ) ),
			//        o => o.Append( "</x:Array>" ).ToString()
			//) );
			dobj.SetData( DataFormats.Bitmap, RenderSelectionAsBitmap() );
			Clipboard.SetDataObject( dobj );
		}

		private void DeleteSelected() {
			List<object> del = new List<object>();

			del.AddRange( SelectedItems.OfType<object>() );

			foreach (object o in del)
				Items.Remove( o );
		}

		private ImageSource RenderSelectionAsBitmap() {
			//TODO: Optimise Render selection to bitmap
			double x = double.MaxValue,
					y = double.MaxValue,
					width = 0,
					height = 0;

			Point origin = new Point( 0, 0 );

			foreach (UIElement v in SelectedItems.OfType<UIElement>()) {
				//Get Location of Plot
				GeneralTransform t = v.TransformToAncestor( this );
				Point p = t.Transform( origin );

				//Ammend selection bounds
				x = Math.Min( x, p.X );
				y = Math.Min( y, p.Y );
				width = Math.Max( width, p.X + v.RenderSize.Width );
				height = Math.Max( height, p.Y + v.RenderSize.Height );
			}
			//Create Bitmap Buffer
			RenderTargetBitmap bmp = new RenderTargetBitmap( (int)(width + .5d - x), (int)(height + .5d - y), 96, 96, PixelFormats.Default );

			DrawingVisual dv = new DrawingVisual();
			Insets insets = new Insets( x, y, 0, 0 );

			using (DrawingContext cx = dv.RenderOpen()) {
				cx.DrawRectangle( Background, null, new Rect( new Size( width, height ) ) );
				foreach (UIElement uiEl in SelectedItems.OfType<UIElement>()) {
					//Get Location of Plot
					GeneralTransform t = uiEl.TransformToAncestor( this );
					Point p = t.Transform( origin );

					cx.DrawRectangle( new VisualBrush( uiEl ), null, new Rect( new Point( p.X - x, p.Y - y ), uiEl.RenderSize ) );
				}
			}
			bmp.Render( dv as Visual );

			return bmp;
		}

		protected override void OnMouseLeftButtonUp( MouseButtonEventArgs e ) {
			if (e.Source == this)
				SelectedItems.Clear();
		}

		protected virtual void OnChildMouseLeftButtonUp( object sender, MouseButtonEventArgs e ) {
			if (Items.Contains( e.Source ))
				if (Keyboard.Modifiers.HasFlag( ModifierKeys.Control ) && SelectedItems.Contains( e.Source ))
					SelectedItems.Remove( e.Source );
				else if (Keyboard.Modifiers.HasFlag( ModifierKeys.Control ) || Keyboard.Modifiers.HasFlag( ModifierKeys.Shift ))
					SelectedItems.Add( e.Source );
				else if (!SelectedItems.Contains( e.Source )) {
					SelectedItems.Clear();
					SelectedItems.Add( e.Source );
				}
		}

		public XmlElement XMLSerialise( XmlDocument doc ) {
			XmlElement sheet = doc.CreateElement( "mesuro:sheet" );
			foreach (IXmlSerialisable child in Items.OfType<IXmlSerialisable>()) {
				sheet.AppendChild( child.XMLSerialise( doc ) );
			}
			foreach (RichTextBox child in Items.OfType<RichTextBox>()) {
				XmlElement elText = doc.CreateElement( "mesuro:text" );
				using (MemoryStream ms = new MemoryStream()) {
					new TextRange( child.Document.ContentStart, child.Document.ContentEnd ).Save( ms, DataFormats.Rtf );
					ms.Position = 0;
					elText.AppendChild( doc.CreateCDataSection( new StreamReader( ms ).ReadToEnd() ) );
				}
				sheet.AppendChild( elText );
			}
			foreach (Image child in Items.OfType<Image>()) {
				//sheet.AppendChild( SerialiseImage( doc ) );
			}
			return sheet;
		}

		#region Commands
		private void cmdDelete_CanExecute( object sender, CanExecuteRoutedEventArgs e ) {
			e.CanExecute = SelectedItems.Count > 0;
		}
		public void cmdDelete_Executed( object sender, ExecutedRoutedEventArgs e ) {
			DeleteSelected();
		}

		private void NewGraphCommand_Executed( object sender, ExecutedRoutedEventArgs e ) {
			Items.Add( new Graph() { AxesType = GraphingRegistry.DefaultAxesType } );
		}

		private void SetGraphAxesCommand_CanExecute( object sender, CanExecuteRoutedEventArgs e ) {
			e.CanExecute = SelectedItems.Count > 0;
		}
		private void SetGraphAxesCommand_Executed( object sender, ExecutedRoutedEventArgs e ) {
			foreach (Object i in SelectedItems)
				if (i is Graph)
					((Graph)i).AxesType = (String)e.Parameter;
		}

		private void cmdObjectOrder_CanExecute( object sender, CanExecuteRoutedEventArgs e ) {
			e.CanExecute = SelectedItems.Count > 0;
		}
		private void cmdObjectOrder_Executed( object sender, ExecutedRoutedEventArgs e ) {
			switch ((string)e.Parameter) {
				case "Front": ObjectOrderFront(); break;
				case "Raise": ObjectOrderRaise(); break;
				case "Lower": ObjectOrderLower(); break;
				case "Back": ObjectOrderBack(); break;
			}
		}

		private void cmdCopy_CanExecute( object sender, CanExecuteRoutedEventArgs e ) {
			e.CanExecute = SelectedItems.Count > 0;
		}
		private void cmdCopy_Executed( object sender, ExecutedRoutedEventArgs e ) {
			CopySelected();
			e.Handled = true;
		}

		private void cmdCut_CanExecute( object sender, CanExecuteRoutedEventArgs e ) {
			e.CanExecute = SelectedItems.Count > 0;
		}
		private void cmdCut_Executed( object sender, ExecutedRoutedEventArgs e ) {
			CopySelected();
			DeleteSelected();
		}

		private void cmdPaste_CanExecute( object sender, CanExecuteRoutedEventArgs e ) {
			e.CanExecute = Clipboard.ContainsData( FORMAT_CanvasHostableEnumeration ) || Clipboard.ContainsText() || Clipboard.ContainsImage();
		}
		private void cmdPaste_Executed( object sender, ExecutedRoutedEventArgs e ) {
			IDataObject dobj = Clipboard.GetDataObject();
			string[] formats = dobj.GetFormats();

			DeleteSelected();

			if (formats.Contains( FORMAT_CanvasHostableEnumeration )) {
				foreach (string xaml in (IEnumerable<string>)dobj.GetData( FORMAT_CanvasHostableEnumeration )) {
					Graph g = XamlReader.Parse( xaml ) as Graph;
					Items.Add( g );
					SelectedItem = g;
					/*
					if (newItems != null)
						foreach (object obj in newItems) {
							Items.Add( obj );
							SelectedItems.Add( obj );
						}
					*/
				}
			} else if (formats.Contains( DataFormats.Bitmap )) {
				//				InsertImage( (BitmapSource)dobj.GetData( DataFormats.Bitmap ) );

			} else if (formats.Contains( DataFormats.Text )) {
				//	InsertText( (string)dobj.GetData( DataFormats.Text ) );

			}
		}

		private void NewTextBoxCommand_Executed( object sender, ExecutedRoutedEventArgs e ) {
			RichTextBox rtb = new RichTextBox() {
				BorderThickness = new Thickness( 0 ),
				IsReadOnly = true,
				Document = new FlowDocument( new Paragraph( new Run( "Some Text..." ) ) ),
				AcceptsTab = true,
				AcceptsReturn = true
			};
			Items.Add( rtb );
		}

		private void NewImageCommand_Executed( object sender, ExecutedRoutedEventArgs e ) {
			string source;
			StringBuilder filterBuilder = new StringBuilder( "All Supported Formats|" );
			filterBuilder.Append( FileFormatRegistry.GetExtensionString( App.FormatCategory_Image ) ).Append( "|" );
			filterBuilder.Append( FileFormatRegistry.GetFilterString( App.FormatCategory_Image ) );
			lock (App.OpenDialog) {
				App.OpenDialog.Reset();
				App.OpenDialog.Title = "Insert Image";
				App.OpenDialog.Filter = filterBuilder.ToString();
				if (App.OpenDialog.ShowDialog() ?? false)
					source = App.OpenDialog.FileName;
				else return;
			}
			try {
				BitmapSource bmp = null;
				FileStream imStrm = new FileStream( source, FileMode.Open );
				switch (source.Substring( source.LastIndexOf( '.' ) )) {
					case ".jpg":
					case ".jpeg":
						bmp = new JpegBitmapDecoder( imStrm, BitmapCreateOptions.None, BitmapCacheOption.Default ).Frames[0];
						break;
					case ".png":
						bmp = new PngBitmapDecoder( imStrm, BitmapCreateOptions.None, BitmapCacheOption.Default ).Frames[0];
						break;
					case ".gif":
						bmp = new GifBitmapDecoder( imStrm, BitmapCreateOptions.None, BitmapCacheOption.Default ).Frames[0];
						break;
					case ".tiff":
						bmp = new TiffBitmapDecoder( imStrm, BitmapCreateOptions.None, BitmapCacheOption.Default ).Frames[0];
						break;
					case ".wmp":
						bmp = new WmpBitmapDecoder( imStrm, BitmapCreateOptions.None, BitmapCacheOption.Default ).Frames[0];
						break;
					case "bmp":
						BitmapImage bi = new BitmapImage();
						bi.BeginInit();
						bi.StreamSource = imStrm;
						bi.EndInit();
						bmp = bi;
						break;
				}
				if (bmp == null)
					throw new Exception( "Bitmap not decoded." );
				Items.Add( new Image() { Source = bmp, Stretch = Stretch.Uniform } );
			} catch (Exception ex) {
				MessageBox.Show( "An error occurred while accessing the image:\r\n" + ex.Message, "Can'selectionType Access Image!", MessageBoxButton.OK, MessageBoxImage.Error );
			}
		}
		#endregion

		private void cmdSetFontFamily_Executed( object sender, ExecutedRoutedEventArgs e ) {
			RichTextBox rtb = SelectedItem as RichTextBox;
			if (rtb != null) {
				rtb.Selection.ApplyPropertyValue( TextElement.FontFamilyProperty, e.Parameter );
			}
		}

		private void cmdSetFontFamily_CanExecute( object sender, CanExecuteRoutedEventArgs e ) {
			e.CanExecute = SelectedItem is RichTextBox && e.Parameter is string;
		}


		private void cmdSetFontSize_Executed( object sender, ExecutedRoutedEventArgs e ) {
			RichTextBox rtb = SelectedItem as RichTextBox;
			if (rtb != null) {
				rtb.Selection.ApplyPropertyValue( TextElement.FontSizeProperty, e.Parameter );
			}
		}

		private void cmdSetFontSize_CanExecute( object sender, CanExecuteRoutedEventArgs e ) {
			e.CanExecute = SelectedItem is RichTextBox && (e.Parameter is double || e.Parameter is float || e.Parameter is int);
		}
	}
}
